#ifndef CODEEDITOR_H
#define CODEEDITOR_H

#include <QPlainTextEdit>
#include <QObject>

#include "IFile.h"
#include "ICodeEditor.h"
#include "Settings/FontSettings.h"
#include "Settings/DisplaySettings.h"
#include "Settings/TabSettings.h"
#include "Settings/StorageSettings.h"
#include "BookmarkManager.h"
#include "IFindSupport.h"

QT_BEGIN_NAMESPACE
class QPaintEvent;
class QResizeEvent;
class QSize;
class QWidget;
QT_END_NAMESPACE

namespace CodeEdit {
    class FileDocument;
    namespace Internal {
        class CodeEditorPrivate;
        class CodeEditorOverlay;
    }
}

using namespace CodeEdit;

//! @class CodeEditor
class CodeEditor : public QPlainTextEdit
{
    Q_OBJECT

public:
    CodeEditor(QWidget *parent = 0);
    ~CodeEditor();

    void setSyntaxHighlighter(QSyntaxHighlighter*);

    // EditorInterface
    Core::IFile *file();
    bool createNew(const QString &contents);
    bool open(const QString &fileName = QString());
    bool save(const QString &fileName = QString());
    QByteArray saveState() const;
    bool restoreState(const QByteArray &state);
    QString displayName() const;

    // ITextEditor
    int find(const QString &exp) const;

    int currentLine() const;
    int currentColumn() const;
    void gotoLine(int line, int column = 0);

    int position(ICodeEditor::PositionOperation posOp = ICodeEditor::Current, int at = -1) const;
    void convertPosition(int pos, int *line, int *column) const;

    QString contents() const;
    QString selectedText() const;
    QString textAt(int pos, int length) const;
    QChar characterAt(int pos) const;

    QTextCodec *textCodec() const;
    void setTextCodec(QTextCodec *codec);

    // ITextEditable
    void remove(int length);
    void insert(const QString &string);
    void replace(int length, const QString &string);
    void setCursorPos(int pos);
    void select(int toPos);

    void print(QPrinter *printer);

    void setSuggestedFileName(const QString &suggestedFileName);
    QString mimeType() const;
    void setMimeType(const QString &mt);

    // Works only in conjunction with a syntax highlighter that puts
    // parentheses into text block user data
    bool isParenthesesMatchingEnabled() const;
    void setParenthesesMatchingEnabled(bool b);

    bool isAutoParenthesesEnabled() const;
    void setAutoParenthesesEnabled(bool b);

    bool highlightCurrentLine() const;
    void setHighlightCurrentLine(bool b);

    bool lineNumbersVisible() const;
    void setLineNumbersVisible(bool b);

    bool marksVisible() const;
    void setMarksVisible(bool b);

    bool requestMarkEnabled() const;
    void setRequestMarkEnabled(bool b);

    bool lineSeparatorAllowed() const;
    void setLineSeparatorAllowed(bool b);

    bool codeFoldingVisible() const;
    void setCodeFoldingVisible(bool b);

    bool codeFoldingSupported() const;
    void setCodeFoldingSupported(bool b);

    bool revisionsVisible() const;
    void setRevisionsVisible(bool b);

    int visibleWrapColumn() const;
    void setVisibleWrapColumn(int column);

    QObject *actionHack() const;
    void setActionHack(QObject *);

    virtual void setReadOnly(bool ro);
    virtual void setTextCursor(const QTextCursor &cursor);

    int verticalBlockSelection() const;

    int fontZoom() const;
    void setFontZoom(int);

public slots:
    void setDisplayName(const QString &title);

    void paste();
    void cut();

    void zoomIn();
    void zoomOut();
    void zoomReset();

    void cutLine();
    void deleteLine();
    void unfoldAll();
    void fold();
    void unfold();
    void selectEncoding();

    void gotoBlockStart(bool select = false);
    void gotoBlockEnd(bool select = false);

    void gotoLineStart(bool select = false);
    void gotoLineEnd(bool select = false);
    void gotoPreviousLine(bool select = false);
    void gotoNextLine(bool select = false);
    void gotoPreviousCharacter(bool select = false);
    void gotoNextCharacter(bool select = false);
    void gotoPreviousWord(bool select = false);
    void gotoNextWord(bool select = false);

    void selectBlockUp();
    void selectBlockDown();

    void moveLineUp();
    void moveLineDown();

    void copyLineUp();
    void copyLineDown();

    void joinLines();

    void insertLineAbove();
    void insertLineBelow();

    void cleanWhitespace();

signals:
    void fontZoomChanged(int);
    void fileDropped(const QString&);
    void baseFontChanged(const QFont&);
    void codecChanged(QTextCodec*);

    void changed();
    void contentsChanged(); // ICodeEditor

    void requestBlockUpdate(const QTextBlock&);

    void contentsChangedBecauseOfUndo();
    void markRequested(int line);
    void markContextMenuRequested(int line, QMenu *menu);
    void tooltipRequested(const QPoint &globalPos, int position);
    void contextHelpIdRequested(int position);

public:
    //! [color settings]
    QColor wrapEdgeFillColor();
    void setWrapEdgeFillColor(QColor);

    QColor wrapEdgeColor();
    void setWrapEdgeColor(QColor);

    int wrapColumn();
    void setWrapColumn(int);

    QColor currentLineFillColor();
    void setCurrentLineFillColor(QColor);

    QColor lineNumbersFillColor();
    void setLineNumbersFillColor(QColor);

    QColor lineNumbersColor();
    void setlineNumbersColor(QColor);

    QColor foldingMarkersFillColor();
    void setFoldingMarkersFillColor(QColor);

//! @group reimplemented events
protected:
    //! to set geometry of inner widgets
    virtual void resizeEvent(QResizeEvent*);

    //! to support wheel zooming
    virtual void wheelEvent(QWheelEvent*);

    //! to provide custom processing
    virtual bool event(QEvent*);
    virtual void keyPressEvent(QKeyEvent*);

    //! to paint right margin area
    virtual void paintEvent(QPaintEvent*);

    //! to support file dropping
    virtual void dragEnterEvent(QDragEnterEvent*);
    virtual void dragMoveEvent(QDragMoveEvent*);
    virtual void dropEvent(QDropEvent*);

    virtual void changeEvent(QEvent *event);

    //! to support block selection
    virtual QMimeData *createMimeDataFromSelection() const;
    virtual bool canInsertFromMimeData(const QMimeData *source) const;
    virtual void insertFromMimeData(const QMimeData *source);

    virtual bool viewportEvent(QEvent *event);
    virtual void timerEvent(QTimerEvent *event);

    virtual void mouseMoveEvent(QMouseEvent *event);
    virtual void mousePressEvent(QMouseEvent *event);

private:
    void maybeSelectLine();

protected:
    FileDocument *fileDocument() const;
    void setFileDocument(FileDocument *doc);

    void setDefaultPath(const QString &defaultPath);

private slots:
    void editorContentsChanged(int position, int charsRemoved, int charsAdded);
    void memorizeCursorPosition();
    void restoreCursorPosition();
    void highlightSearchResults(const QString &txt, Find::IFindSupport::FindFlags findFlags);
    void setFindScope(const QTextCursor &start, const QTextCursor &end, int);
    void requestMarkContextMenu(int line, QMenu* contextMenu);
    void requestMark(int line);
    void requestMark();

public:
    Bookmarks::BookmarkManager *bookmarkManager() const;

private:
    Bookmarks::BookmarkManager *_bookmarkManager;

private:
    Internal::CodeEditorPrivate *d;
    friend class Internal::CodeEditorPrivate;
    friend class Internal::CodeEditorOverlay;

public:
    QWidget *extraArea() const;
    int extraAreaWidth(int *markWidthPtr = 0) const;
    void extraAreaPaintEvent(QPaintEvent* event);
    void extraAreaMouseEvent(QMouseEvent *event);
    void extraAreaLeaveEvent(QEvent *event);

    const TabSettings &tabSettings() const;
    const DisplaySettings &displaySettings() const;
    const StorageSettings &storageSettings() const;

    void markBlocksAsChanged(QList<int> blockNumbers);

    void ensureCursorVisible();

    enum ExtraSelectionKind {
        CurrentLineSelection,
        ParenthesesMatchingSelection,
        CodeWarningsSelection,
        CodeSemanticsSelection,
        UnusedSymbolSelection,
        OtherSelection,
        NExtraSelectionKinds
    };
    QList<QTextEdit::ExtraSelection> extraSelections(ExtraSelectionKind kind) const;
    void setExtraSelections(ExtraSelectionKind kind, const QList<QTextEdit::ExtraSelection> &selections);

    struct BlockRange
    {
        BlockRange() : first(0), last(-1) { }
        BlockRange(int first_position, int last_position)
            : first(first_position), last(last_position) { }
        int first;
        int last;
        inline bool isNull() const { return last < first; }
    };

    // the blocks list must be sorted
    void setIfdefedOutBlocks(const QList<BlockRange> &blocks);

public slots:
    void format();
    void rewrapParagraph();
    void unCommentSelection();

    void loadSettings();
    void setFontSettings(const CodeEdit::FontSettings&);
    void setTabSettings(const CodeEdit::TabSettings&);
    void setDisplaySettings(const CodeEdit::DisplaySettings&);
    void setStorageSettings(const CodeEdit::StorageSettings&);

public:
    //! Returns true if key triggers an indent.
    bool isElectricCharacter(const QChar &ch) const;

protected:
    //! Returns the text to complete at the cursor position, or an empty string
    virtual QString autoComplete(QTextCursor &cursor, const QString &text) const;
    //! Handles backspace. When returning true, backspace processing is stopped
    virtual bool autoBackspace(QTextCursor &cursor);
    //! Hook to insert special characters on enter. Returns the number of extra blocks inserted.
    virtual int paragraphSeparatorAboutToBeInserted(QTextCursor &cursor);
    //! Indent a text block based on previous line. Default does nothing
    virtual void indentBlock(QTextDocument *doc, QTextBlock block, QChar typedChar);
    //! Indent at cursor. Calls indentBlock for selection or current line.
    virtual void indent(QTextDocument *doc, const QTextCursor &cursor, QChar typedChar);
    //! Reindent at cursor. Selection will be adjusted according to the indentation change of the first block
    virtual void reindent(QTextDocument *doc, const QTextCursor &cursor);

private:
    void onTabKey(QKeyEvent*);
    void onShiftTabKey(QKeyEvent*);
    void indentOrUnindent(bool doIndent);
    void onHomeKey(QKeyEvent*);
    void onReturnKey(QKeyEvent*);
    void onBackspaceKey(QKeyEvent*);
    void onCtrlSlashKey(QKeyEvent*);

    void moveLineUpDown(bool up);
    void copyLineUpDown(bool up);
    void saveCurrentCursorPositionForNavigation();

    void updateHighlights();
    void updateCurrentLineHighlight();

    void drawFoldingMarker(QPainter *painter, const QPalette &pal, const QRect &rect,
                           bool expanded, bool active, bool hovered) const;

    void toggleBlockVisible(const QTextBlock &block);
    QRect foldBox();

    QTextBlock foldedBlockAt(const QPoint &pos, QRect *box = 0) const;

// parentheses matcher
private slots:
    void matchParentheses();
    void highlightBlocks();
    void slotSelectionChanged();
    void animateUpdate(int position, QPointF lastPos, QRectF rect);

private slots:
    void slotUpdateExtraAreaWidth();
    void slotUpdateRequest(const QRect&, int);
    void slotCursorPositionChanged();
    void slotModificationChanged(bool);
    void slotUpdateBlockNotify(const QTextBlock&);
};

#endif
